import MBase from "../utils/m-base";
import MFile from "../utils/m-file";
import MCmder from "../services/m-cmder";
import MWriter from "../services/m-writer";
import { IParsedModel, IReqMethod } from "../m-types";
import { IProp, IType, IParm } from "../m-types";

/**flutter+dio文件书写器 */
export default class WriterFlutterDio extends MWriter {
  /**书写枚举 */
  protected async writeEnums() {}

  /**书写模型 */
  protected async writeModels() {
    const { data, dir } = this;
    for (const m of data.models) {
      const { name, description, props } = m;
      const uses = (m.uses || []).filter((x) => x.name != "map");
      const path = `${dir}/models/${MBase.camelToSnake(name)}.dart`;
      //定义文件框架
      const lines: Array<string> = [];

      //添加文件提示
      lines.push(...this.getFileTips());

      //添加模型引用
      const imports: Array<string> = [];
      imports.push(`// ignore_for_file: unused_import`);
      imports.push(`import 'dart:io';`);
      imports.push(`import 'package:intl/intl.dart';`);
      for (const u of uses.filter((x) => !x.isEnum)) {
        imports.push(`import "./${MBase.camelToSnake(u.file)}.dart";`);
      }
      lines.push(...imports);

      //添加一个空行
      lines.push("\t");

      //添加模型备注
      lines.push(...MBase.toDartNote(description));

      //添加模型定义开头
      lines.push(`class ${name} {`);

      //添加模型属性
      const body: Array<string> = [];
      for (const p of props) body.push(...this.getModelProp(p));
      lines.push(...MBase.ident(body, 2));

      //添加一个空行
      lines.push("\t");

      //添加模型构造器
      lines.push(...this.getModelConstructor(m));
      //添加一个空行
      lines.push("\t");
      //添加模型接收器
      lines.push(...this.getModelFromJson(m));
      //添加一个空行
      lines.push("\t");
      //添加模型序列器
      lines.push(...this.getModelToJson(m));

      //添加模型结尾
      lines.push("}");

      //添加一个空行
      lines.push("\t");

      //写入文件
      await MFile.writeFile(path, MBase.toRows(lines));
    }
  }

  /**书写请求函数 */
  protected async writeMethods() {
    const { data, dir } = this;
    for (const m of data.methods) {
      const group = m.group || "";
      const base = m.base || "";
      const uses = (m.uses || []).filter((x) => x.name != "map");
      const urls = m.urls || [];
      const funcs = m.funcs || [];
      const path = `${dir}/${MBase.camelToSnake(group)}.dart`;
      //定义文件框架
      const lines: Array<string> = [];
      //添加文件提示
      lines.push(...this.getFileTips());
      //添加模型引用
      const imports: Array<string> = [];
      imports.push(`// ignore_for_file: unused_import`);
      imports.push(`import 'dart:io';`);
      imports.push(`import 'package:intl/intl.dart';`);
      imports.push(`import 'package:dio/dio.dart';`);
      //TODO: 优化引用(指向项目根目录)
      imports.push(`import '../../dio_config.dart';`);
      for (const u of uses.filter((x) => !x.isEnum)) {
        imports.push(`import "./models/${MBase.camelToSnake(u.file)}.dart";`);
      }
      lines.push(...imports);
      //添加两个空行
      lines.push("\t\t");
      //添加请求项目名称
      const baseName = `const baseName = "${base}";`;
      lines.push(...[...MBase.toDartNote("定义请求项目名称"), baseName]);
      //添加两个空行
      lines.push("\t\t");
      //添加请求地址
      const paths: Array<string> = [];
      for (const u of urls) {
        const { name, description, value } = u;
        const desc = MBase.toDartNote(description);
        const dim = `const ${name} = "${value}";`;
        paths.push(...[...desc, dim]);
      }
      lines.push(...paths);
      //添加两个空行
      lines.push("\t\t");
      //添加请求函数
      const apis: Array<string> = [];
      for (const f of funcs) {
        apis.push(...this.getMethodFunc(f));
      }
      lines.push(...apis);
      //添加一个空行
      lines.push("\t");
      //写入文件
      await MFile.writeFile(path, MBase.toRows(lines));
    }
  }

  /**获取文件顶部提示内容 */
  protected getFileTips() {
    const cmd = MCmder.encodeArgs(this.parm);
    return [
      ...MBase.toCommonNote("此文件代码由接口发布器swg生成，请勿手动修改。"),
      ...MBase.toCommonNote(`命令行中执行 '${cmd["cmd"]}' 将重新生成。`),
      "\t",
    ];
  }

  /**获取模型属性定义字符串 */
  private getModelProp(prop: IProp) {
    const n = (prop.name || "").replace(/^_+/, "");
    const name = MBase.snakeToCamel(n, true);
    const description = prop.description || "";
    const note = MBase.toDartNote(description);
    const typeStr = this.getModelTypeStr(prop);
    const dim = `${typeStr} ${name};`;
    return [...note, dim];
  }

  /**获取模型构造函数定义字符串 */
  private getModelConstructor(model: IParsedModel) {
    const m = model || {};
    const props = m.props || [];
    const mName = m.name || "";
    const desc = m.description || "";
    const lines: Array<string> = [];
    const hasProps = props.length > 0;
    lines.push(...MBase.toDartNote(desc || `实例化${mName}`));
    lines.push(`${mName}(${hasProps ? "{" : ""}`);
    const body: Array<string> = [];
    for (const p of props) {
      const { required, nullable } = p;
      const n = (p.name || "").replace(/^_+/, "");
      const name = MBase.snakeToCamel(n, true);
      const typeStr = this.getModelTypeStr(p);
      const value = this.getModelValueStr(p);
      const must = required || !nullable;
      const need = /^list</i.test(typeStr) ? false : must;
      const val = value && value != "null" ? ` = ${value}` : "";
      body.push(`${need ? "required " : ""}this.${name}${val},`);
    }
    lines.push(...MBase.ident(body, 2));
    lines.push(`${hasProps ? "}" : ""});`);
    return MBase.ident(lines, 2);
  }

  /**获取模型接收器符串 */
  protected getModelFromJson(model: IParsedModel) {
    const m = model || {};
    const props = m.props || [];
    const mName = m.name || "";
    const desc = m.description || "";
    const ignores = "File,Blob".split(",");
    const bases = "int,double,bool,String,dynamic".split(",");
    const lines: Array<string> = [];
    const hasProps = props.length > 0;
    lines.push(...MBase.toDartNote(desc || `反序列化化${mName}`));
    lines.push(
      `${mName}.fromJson(Map<String, dynamic> json)${hasProps ? ":" : ";"}`
    );
    const body: Array<string> = [];
    for (const p of props) {
      const n = (p.name || "").replace(/^_+/, "");
      const name = MBase.snakeToCamel(n, true);
      const type = this.getModelTypeStr(p).replace(/\?$/, "");
      const subType = (type.split("List<")[1] || "").split(">")[0];
      let val = "";
      if (bases.includes(type) || type.includes("Map<")) {
        val = `json['${name}']`;
      } else if (ignores.includes(type)) {
        val = "null";
      } else if (/^List</.test(type) && bases.includes(subType)) {
        val = `json['${name}'] ?? []`;
      } else if (/^List</.test(type)) {
        val = `((json['${name}'] ?? []) as List<dynamic>).map((m)=> ${subType}.fromJson(m)).toList()`;
      } else if (type == "DateTime") {
        val = `json['${name}'] != null? DateFormat("yyyy-MM-ddTHH:mm:ss").parse(json['${name}']): null`;
      } else {
        val = `json['${name}'] != null ? ${type}.fromJson(json['${name}']) : null`;
      }
      body.push(`${name} = ${val},`);
    }
    if (body.length > 0) {
      body[body.length - 1] = body[body.length - 1].replace(/\,$/, ";");
    }
    lines.push(...MBase.ident(body, 2));
    return MBase.ident(lines, 2);
  }

  /**获取模型序列器符串 */
  protected getModelToJson(model: IParsedModel) {
    const m = model || {};
    const props = m.props || [];
    const mName = m.name || "";
    const desc = m.description || "";
    const ignores = "File,Blob".split(",");
    const bases = "int,double,bool,String,dynamic".split(",");
    const lines: Array<string> = [];
    lines.push(...MBase.toDartNote(desc || `序列化${mName}`));
    lines.push(`Map<String,dynamic> toJson() => {`);
    const body: Array<string> = [];
    for (const p of props) {
      const n = (p.name || "").replace(/^_+/, "");
      const name = MBase.snakeToCamel(n, true);
      const must = p.required || !p.nullable;
      const type = this.getModelTypeStr(p).replace(/\?$/, "");
      const subType = (type.split("List<")[1] || "").split(">")[0];
      let val = "";
      if (bases.includes(type) || type.includes("Map<")) {
        val = `${name}`;
      } else if (ignores.includes(type)) {
        val = `null`;
      } else if (/^List</.test(type) && bases.includes(subType)) {
        val = `${name}`;
      } else if (/^List</.test(type)) {
        val = `${name}.map((m)=> m.toJson()).toList()`;
      } else if (type == "DateTime") {
        val = `${name} != null? DateFormat("yyyy-MM-ddTHH:mm:ss").format(${name}!): null`;
      } else {
        val = `${name}${!must ? "?" : ""}.toJson()`;
      }
      body.push(`"${name}": ${val},`);
    }
    lines.push(...MBase.ident(body, 2));
    lines.push(`};`);
    return MBase.ident(lines, 2);
  }

  /**获取请求函数定义 */
  private getMethodFunc(func: IReqMethod) {
    const { name, method, urlName, description } = func || {};
    const { inputParms, inputModel, outputModel } = func || {};
    const bases = "int,double,bool,String,dynamic".split(",");
    const ignores = "File,Blob".split(",");
    const parm = this.getMethodParm(inputParms);
    const parms = parm.filter((x) => !x.inPath).map((x) => x.prop);
    const paths = parm.filter((x) => x.inPath).map((x) => x.prop);
    const input = this.getTypeStr(inputModel);
    const output = this.getTypeStr(outputModel) || "dynamic";
    //定义函数框架
    const lines: Array<string> = [];
    //添加函数注释
    lines.push(...MBase.toDartNote(description));

    //添加函数定义
    let mParm = "{";
    if (input) mParm = `${mParm}required ${input} body, `;
    parm.forEach((x) => (mParm = `${mParm}${x.dim}, `));
    mParm = `${mParm}Options? options, CancelToken? canceller`;
    if (method != "delete") mParm = `${mParm}, ProgressCallback? onProgress`;
    mParm = `${mParm}}`;
    const mRes = output ? `Future<${output}>` : `Future`;
    lines.push(`${mRes} ${name}(${mParm}) async {`);

    //添加函数任务
    const task: Array<string> = [];
    task.push(`String reqPath = ${urlName};`);
    task.push(`Options reqConf = options ?? Options();`);
    task.push(`Map<String,dynamic> reqHeaders = reqConf.headers ?? {};`);
    task.push(`reqHeaders["baseName"] = baseName;`);
    task.push(`reqConf.headers = reqHeaders;`);
    const parmList = [`options: reqConf,`, `cancelToken: canceller,`];

    if (input && input.includes("Map<")) {
      task.push(
        `body.forEach((k, v) => v is File ? body[k] = MultipartFile.fromFileSync(v.path) : body[k] = v);`
      );
      task.push(`final reqBody = FormData.fromMap(body);`);
      parmList.unshift(`data: reqBody,`);
    } else if (input) {
      parmList.unshift(`data: body,`);
    }

    //添加请求头处理

    //添加请求地址处理
    if (paths.length > 0) {
      task.push(`Map<String,dynamic> reqPaths = { ${paths.join(", ")} };`);
      task.push(
        `reqPaths.forEach((k, v) => reqPath = reqPath.replaceAll('{$k}', v?.toString() ?? ''));`
      );
    }
    if (parms.length > 0) {
      task.push(`Map<String,dynamic> resParms = { ${parms.join(", ")} };`);
      parmList.unshift(`Uri(path: reqPath, queryParameters: resParms),`);
    } else {
      parmList.unshift(`Uri(path: reqPath),`);
    }
    if (method != "delete") parmList.push(`onReceiveProgress: onProgress,`);
    //添加dio定义
    task.push(`final dioRes = await dio.${method}Uri(`);
    task.push(...MBase.ident(parmList, 2));
    task.push(");");

    //添加返回值处理
    if (bases.includes(output) || ignores.includes(output)) {
      task.push(`return dioRes.data;`);
    } else if (/^List</i.test(output)) {
      const sub = (output.split("List<")[1] || "").split(">")[0];
      if (bases.includes(sub)) {
        task.push(`return dioRes.data;`);
      } else {
        task.push(`return dioRes.data.map((m)=> ${sub}.fromJson(m)).toList();`);
      }
    } else if (output == "DateTime") {
      task.push(`return DateFormat("yyyy-MM-ddTHH:mm:ss").parse(dioRes.data);`);
    } else {
      task.push(`return ${output}.fromJson(dioRes.data);`);
    }

    //把任务定义添加到文件框架中
    lines.push(...MBase.ident(task, 2));

    //添加函数结尾
    lines.push("}");

    return lines;
  }

  /**获取请求函数参数 */
  private getMethodParm(parm: Array<IParm>) {
    const dims = parm.map((x) => {
      const name = x.name || "";
      const sign = !x.inPath ? "?" : "";
      const type = this.getTypeStr(x.type);
      if (!name) return { name: "", inPath: false, prop: "", dim: "" };
      return {
        name,
        inPath: x.inPath,
        prop: `"${name}": ${name}`,
        dim: `${type}${sign} ${name}`,
      };
    });
    return dims.filter((x) => !!x.name);
  }

  /**获取类型字符串 */
  private getModelTypeStr(prop: IProp) {
    const { type, required, nullable } = prop || {};
    const nulls = "File,DateTime,dynamic".split(",");
    const must = required || !nullable;
    const str = this.getTypeStr(type);
    if (/^List</.test(str)) return str;
    if (nulls.includes(str)) return `${str}?`;
    return must ? str : `${str}?`;
  }

  /**获取默认值字符串 */
  private getModelValueStr(prop: IProp) {
    const { type, required, nullable } = prop || {};
    const first = (type || [])[0] || {};
    const fType = first.type;
    const singles = "long,short,int,integer,int32,int64".split(",");
    const doubles = "number,double,float,decimal".split(",");
    if (fType == "array") return "const []";
    if (required) return "";
    if (nullable) return "null";
    if (singles.includes(fType)) return "0";
    if (doubles.includes(fType)) return "0.0";
    if (fType === "string") return `""`;
    if (fType === "boolean") return "false";
    return ``;
  }

  /**获取类型字符串 */
  private getTypeStr(type: Array<IType>) {
    const singles = "long,short,integer,int32,int64,int".split(",");
    const doubles = "number,double,float,decimal".split(",");
    const objs = "object,blob".split(",");
    const types = (type || []).slice(0).reverse();
    //有多个类型的全部定义为any类型
    if (types.some((x) => x.type.includes(","))) return "dynamic";
    if (types.length > 0 && types[0].type == "map") {
      return "Map<String, dynamic>";
    }
    //类型链
    let str = "";
    for (const t of types) {
      const format = t.format || "";
      let tp = t.isEnum ? t.enumType || "string" : t.type;
      singles.forEach((x) => (tp = tp.replace(new RegExp(`^${x}$`), "int")));
      doubles.forEach((x) => (tp = tp.replace(new RegExp(`^${x}$`), "double")));
      objs.forEach((x) => (tp = tp.replace(new RegExp(`^${x}$`), "dynamic")));
      if (tp == "file") tp = "File";
      if (tp == "array") tp = "List";
      if (format.includes("date")) tp = "DateTime";
      tp = tp == "boolean" ? "bool" : tp;
      tp = tp == "string" ? "String" : tp;
      str = str ? `${tp}<${str}>` : tp;
    }
    return str;
  }
}
/**注册flutter+dio书写器 */
MWriter.register("flutter+dio", WriterFlutterDio);
